package org.web.clientlistmanager.client.config;

import lombok.Getter;
import lombok.Setter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.validation.annotation.Validated;
import org.web.clientlistmanager.client.biz.ClmClientMonitor;
import org.web.clientlistmanager.client.biz.ClmNettyClientHandler;
import org.web.clientlistmanager.sdk.dto.LocalClientProcessDTO;
import org.web.clientlistmanager.client.domain.AccessServerDTO;
import org.web.clientlistmanager.sdk.dto.ClientListManagerConstant;
import org.web.clientlistmanager.sdk.dto.ClmRequest;
import org.web.clientlistmanager.sdk.event.EventEnum;

import javax.annotation.Resource;
import javax.validation.constraints.NotBlank;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

/**
 * ClientListManager客户端启动类，负责启动架构客户端。
 * 1、采用springboot-starter 做为加载方式。
 * 2、init() 方法做为启动配置的入口，在启动时被调用。
 *
 * @author luyl
 */
@Configuration("clmClientConfig")
@PropertySource(value = {"classpath:application.properties"}, encoding = "utf-8", ignoreResourceNotFound = true)
@Validated
@Setter
@Getter
@ComponentScan(basePackages = {"org.web.clientlistmanager.client", "org.web.clientlistmanager.sdk"})
public class ClmClientConfig implements ClientListManagerConstant, SmartInitializingSingleton {

    private static final Logger logger = LogManager.getLogger(ClmClientConfig.class);

    /**
     * 服务端访问地址列表，
     * （1）多个 IP 和端口应该用分号 (;) 分隔。
     * （2）不能为空，为空则抛异常，会导致启动失败。
     * （3）格式样例：格式为：192.168.1.1:8081;192.168.1.2:8081，解析异常会导致启动失败。
     */
    @NotBlank(message = "accessServerList 必须设置。多个 IP 和端口应该用分号 (;) 分隔。")
    @Value("${org.web.clientlistmanager.client.accessServerList}")
    private String accessServerList;

    @Resource
    private LocalClientProcessDTO localClientProcessDTO;

    @Resource
    private ClmClientMonitor clmClientMonitor;

    @Resource
    private ClmNettyClientHandler clmNettyClientHandler;

    @Autowired
    private BeanFactory beanFactory;

    /**
     * 用于注册的事件请求对象。
     */
    private ClmRequest<LocalClientProcessDTO> reportSelfEventRequest;

    /**
     * 用于注册的事件请求对象。
     */
    private ClmRequest<String> heartBeatEventRequest;

    /**
     * 在 bean 构建完成后进行初始化配置。
     * 通过 @PostConstruct 注解确保该方法在 bean 创建后被调用。
     * 功能：
     * （1）获取配置文件中的 accessServerList 值，解析为 AccessServerDTO 对象的列表。
     * （2）设置 ClmClientMonitor 的 accessServerArrayList 属性。
     * （3）启动 ClmClientMonitor 连接监控。
     */
    @Override
    public void afterSingletonsInstantiated() throws RuntimeException {
        // 解析 accessServerList，并设置到 recoveryServerConnectionMonitor 中
        try {
            List<AccessServerDTO> accessServerDTOList = parseAccessServerList();

            clmClientMonitor.setAccessServerArrayList(accessServerDTOList);
            initLocalClientProcessDO(localClientProcessDTO);
            LocalClientProcessDTO clearLocalClientProcessDTO = new LocalClientProcessDTO();
            // 原来的是带有CGLIB的对象，想要一个干净的非代理对象。
            BeanUtils.copyProperties(localClientProcessDTO, clearLocalClientProcessDTO);
            setLocalClientProcessDTO(clearLocalClientProcessDTO);

            ClmRequest reportSelfEventRequest = buildReportSelfEventRequestByLocalClientProcessDO(clearLocalClientProcessDTO);
            setReportSelfEventRequest(reportSelfEventRequest);

            ClmRequest<String> heartBeatEventRequest = buildHeartBeatEventRequestByLocalClientProcessDO(clearLocalClientProcessDTO);
            setHeartBeatEventRequest(heartBeatEventRequest);

            clmClientMonitor.setClmClientConfig(this);
            clmNettyClientHandler.setClmClientConfig(this);
            // 启动 ClmClientMonitor 监控线程。
            clmClientMonitor.start();
        } catch (Exception e) {
            logger.error("启动失败: " + e.getMessage(), e);
            throw new RuntimeException("启动失败: " + e.getMessage());
        }
    }

    /**
     * 该方法用于启后，二开使用, 用于初始化，定制注册信息。 此处调用有bug,怎么改。
     */
    public ClmRequest<LocalClientProcessDTO> buildReportSelfEventRequestByLocalClientProcessDO(LocalClientProcessDTO localClientProcessDTO) {
        ClmRequest request = new ClmRequest();
        request.setEventName(EventEnum.clmReportSelfEvent.name());
        request.setClientProcessUuid(localClientProcessDTO.getProcessUuid());
        request.setContent(localClientProcessDTO);
        return request;
    }


    public void initLocalClientProcessDO(LocalClientProcessDTO localClientProcessDTO) throws Exception {

        localClientProcessDTO.setProcessUuid(java.util.UUID.randomUUID().toString());
        // 获取RuntimeMXBean实例
        RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
        // 获取进程ID
        String processId = runtimeBean.getName().split("@")[0];
        localClientProcessDTO.setProcessNum(processId);
        localClientProcessDTO.setProcessStartTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        localClientProcessDTO.setProcessUuid(UUID.randomUUID().toString());
        localClientProcessDTO.initClientAddress();

    }


    public ClmRequest<String> buildHeartBeatEventRequestByLocalClientProcessDO(LocalClientProcessDTO localClientProcessDTO) {
        ClmRequest<String> request = new ClmRequest<>();
        request.setEventName(EventEnum.clmHeartbeatEvent.name());
        request.setClientProcessUuid(localClientProcessDTO.getProcessUuid());
        return request;
    }

    /**
     * 解析 accessServerList，返回 AccessServerDTO 对象的列表。
     *
     * @return AccessServerDTO 对象列表。
     * @throws IllegalArgumentException 如果 accessServerList 的格式无效。
     */
    private List<AccessServerDTO> parseAccessServerList() {
        // 通过分号拆分 accessServerList，得到包含 IP 和端口信息的数组
        String[] accessArray = accessServerList.split(";");
        List<AccessServerDTO> arrayList = new ArrayList<>();

        // 遍历数组，将每个 IP:端口 字符串解析为 AccessServerDTO 对象
        for (String access : accessArray) {
            AccessServerDTO accessServerDTO = new AccessServerDTO();
            String[] oneAccessInfo = access.split(":");

            // 检查解析后的数组长度是否为 2，如果不是则抛出异常
            if (oneAccessInfo.length != 2) {
                throw new IllegalArgumentException("格式无效: " + accessServerList);
            }

            // 设置 AccessServerDTO 的 IP 和端口属性
            accessServerDTO.setIp(oneAccessInfo[0].trim());
            accessServerDTO.setPort(Integer.parseInt(oneAccessInfo[1].trim()));

            // 将 AccessServerDTO 添加到列表中
            arrayList.add(accessServerDTO);
        }
        return arrayList;
    }
}
